package com.xuecheng.content.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.api.client.MediaClient;
import com.xuecheng.api.dto.MediaFiles;
import com.xuecheng.api.dto.TeachPlanAndMediaDTO;
import com.xuecheng.base.constant.CourseAuditStatusConstant;
import com.xuecheng.base.constant.MediaObjectAuditConstant;
import com.xuecheng.content.exception.*;
import com.xuecheng.content.mapper.CourseBaseMapper;
import com.xuecheng.content.mapper.TeachplanMapper;
import com.xuecheng.content.mapper.TeachplanMediaMapper;
import com.xuecheng.content.model.dto.BindTeachPlanMediaDTO;
import com.xuecheng.content.model.dto.SaveOrUpdateTeachPlanDTO;
import com.xuecheng.content.model.dto.TeachPlanDTO;
import com.xuecheng.content.model.po.CourseBase;
import com.xuecheng.content.model.po.Teachplan;
import com.xuecheng.content.model.po.TeachplanMedia;
import com.xuecheng.content.service.ITeachPlanService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 课程计划 服务实现类
 * </p>
 *
 * @author 勾新杰
 * @since 2024-12-21
 */
@Service
@RequiredArgsConstructor
public class TeachPlanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements ITeachPlanService {

    private final TeachplanMapper teachplanMapper; // 课程计划mapper

    private final TeachplanMediaMapper teachplanMediaMapper; // 课程计划媒资mapper

    private final CourseBaseMapper courseBaseMapper; // 课程基本信息mapper

    private final MediaClient mediaClient; // 媒资服务feign客户端

    /**
     * 查询课程计划树形结构
     *
     * @param courseId 课程id
     * @return 课程计划树形结构
     */
    @Override
    public List<TeachPlanDTO> queryTreeNodes(Long courseId) {
        // 1. 查询课程计划信息，用DTO直接作为返回结果可以减少属性拷贝，查询这个的时候需要查询并排序，方式就是大章节在小章节前面，同章节等级按照order by字段排序
        List<TeachPlanDTO> teachPlanDTOList = teachplanMapper.selectByCourseId(courseId);
        List<TeachPlanDTO> result = new ArrayList<>();
        // 2. 将返回结果转成map
        Map<Long, TeachPlanDTO> map = teachPlanDTOList.stream()
                .collect(Collectors.toMap(TeachPlanDTO::getId, value -> value));
        // 3. 查询课程计划媒资信息，并添加到指定课程计划对象中
        teachplanMediaMapper.selectByCourseId(courseId)
                .forEach(teachplanMedia ->
                        map.get(teachplanMedia.getTeachplanId()).setTeachplanMedia(teachplanMedia));
        // 4. 构建课程计划之间的树形结构
        teachPlanDTOList.forEach(teachPlanDTO -> { // 要用List集合遍历D！Map存放后遍历的其实是内部的哈希表，显然顺序被打乱了。
            Long parentId = teachPlanDTO.getParentid();
            // 4.1 如果当前课程计划的父id等于0，则添加到返回结果集合中
            if (parentId == 0) result.add(teachPlanDTO);
            else { // 4.2 如果当前课程计划的父id不等于0，则添加到父课程计划对象的children集合中
                TeachPlanDTO parent = map.get(parentId);
                if (parent.getTeachPlanTreeNodes() == null) parent.setTeachPlanTreeNodes(new ArrayList<>());
                parent.getTeachPlanTreeNodes().add(teachPlanDTO);
            }
        });
        // 5. 返回结果
        return result;
    }

    /**
     * 添加课程计划
     *
     * @param saveTeachPlanDTO 添加课程计划信息
     */
    @Override
    public void save(SaveOrUpdateTeachPlanDTO saveTeachPlanDTO) {
        Long parentId = saveTeachPlanDTO.getParentid();
        // 0. 校验课程等级是否合法
        if (parentId != null) {
            if (parentId == 0) {
                if (saveTeachPlanDTO.getGrade() != 1)
                    CourseGradeIllegalException.cast();
            } else {
                Integer grade = teachplanMapper.selectById(parentId).getGrade();
                if (grade + 1 != saveTeachPlanDTO.getGrade())
                    CourseGradeIllegalException.cast();
            }
        }
        Teachplan teachplan = BeanUtil.copyProperties(saveTeachPlanDTO, Teachplan.class);
        // 1.1 查询当级多少章或节，获取当前课程计划的排序号
        Integer max = teachplanMapper. // 这里用mp的selectCount方法就是不行，原因不详
                selectMaxOrder(teachplan.getCourseId(), teachplan.getParentid());
        max = max == null ? 0 : max;
        // 1.2 设置排序号、创建时间、修改时间、课程计划状态
        teachplan.setChangeDate(LocalDateTime.now())
                .setCreateDate(LocalDateTime.now())
                .setOrderby(max + 1);
        // 1.3 插入课程计划
        int insert = teachplanMapper.insert(teachplan);
        if (insert <= 0) CourseFailedSaveTeachPlanException.cast();
    }

    /**
     * 修改课程计划
     *
     * @param updateTeachPlanDTO 修改课程计划信息
     */
    @Override
    public void update(SaveOrUpdateTeachPlanDTO updateTeachPlanDTO) {
        Teachplan teachplan = BeanUtil.copyProperties(updateTeachPlanDTO, Teachplan.class);
        teachplan.setChangeDate(LocalDateTime.now());
        teachplanMapper.updateById(teachplan);
    }

    /**
     * 删除课程计划
     *
     * @param id 课程计划id
     */
    @Override
    @Transactional
    public void deleteById(Long id) {
        Teachplan parent = getById(id);
        if (parent == null) CourseTeachPlanNotExistException.cast();
        CourseBase courseBase = courseBaseMapper.selectById(parent.getCourseId());
        if (courseBase.getCompanyId() != 1232141425L) CourseNotBelongException.cast(); // TODO 公司id暂时写死
        if (courseBase.getAuditStatus().equals(CourseAuditStatusConstant.SUBMITTED))
            CourseHasBeenSubmittedException.cast();
        List<Teachplan> children = teachplanMapper.selectByParentId(id);
        if (!children.isEmpty()) CourseHasChildTeachPlanException.cast();
        else {
            teachplanMapper.deleteById(id);
            teachplanMediaMapper.deleteById(id);
        }
    }

    /**
     * 课程计划上移或者下移
     *
     * @param id 课程计划id
     */
    @Override
    @Transactional
    public void moveDownOrUp(Long id, boolean isDown) {
        // 1. 查询课程计划
        Teachplan teachplan = getById(id);
        if (teachplan == null) CourseTeachPlanNotExistException.cast();
        // 2. 查询同级章节并按照排序字段排序
        List<Teachplan> teachplans = teachplanMapper.selectByCourseIdAndParentId(teachplan.getCourseId(), teachplan.getParentid());
        List<Teachplan> sorted = teachplans.stream()
                .sorted(Comparator.comparing(Teachplan::getOrderby))
                .collect(Collectors.toList());
        // 3. 处理上下移动边界情况
        int index = sorted.indexOf(teachplan);
        if (isDown && index == sorted.size() - 1)
            CourseTeachPlanIsLastException.cast();
        if (!isDown && index == 0)
            CourseTeachPlanIsFirstException.cast();
        // 4. 交换排序号
        Teachplan another = isDown ? sorted.get(index + 1) : sorted.get(index - 1);
        swapOrderBy(teachplan, another);
        // 5. 更新课程计划
        teachplanMapper.updateById(teachplan);
        teachplanMapper.updateById(another);
    }

    /**
     * 交换课程计划的排序号
     *
     * @param t1 课程计划1
     * @param t2 课程计划2
     */
    private void swapOrderBy(Teachplan t1, Teachplan t2) {
        int orderBy = t1.getOrderby();
        t1.setOrderby(t2.getOrderby());
        t2.setOrderby(orderBy);
    }

    /**
     * 课程计划和媒资信息绑定
     *
     * @param bindTeachPlanMediaDTO 课程计划和媒资信息绑定信息
     */
    @Override
    @Transactional
    public void associationMedia(BindTeachPlanMediaDTO bindTeachPlanMediaDTO) {
        // 1.1. 查询课程计划，判断课程计划是否存在以及是否为二级章节
        Teachplan teachplan = teachplanMapper.selectById(bindTeachPlanMediaDTO.getTeachplanId());
        if (teachplan == null) CourseTeachPlanNotExistException.cast();
        if (teachplan.getGrade() != 2) CourseGradeIllegalException.cast();
        // 1.2. 判断媒资信息是否存在以及媒资文件审核状态是否合法
        MediaFiles mediaFiles = mediaClient.queryMediaById(bindTeachPlanMediaDTO.getMediaId());
        if (mediaFiles == null) CourseMediaNotExistException.cast();
        // 1.3. 判断媒资文件是否属于该机构
        CourseBase courseBase = courseBaseMapper.selectById(teachplan.getCourseId());
        if (!Objects.equals(mediaFiles.getCompanyId(), courseBase.getCompanyId()))
            CourseMediaNotBelongException.cast();
        // 1.4 判断媒资信息的审核状态是否为已审核
        if (!MediaObjectAuditConstant.AUDIT_SUCCESS.equals(mediaFiles.getAuditStatus()))
            CourseMediaAuditNotSuccessException.cast();
        // 2. 删除原本该课程计划和媒资信息的关联信息
        teachplanMediaMapper.deleteByTeachPlanId(bindTeachPlanMediaDTO.getTeachplanId());
        // 3. 准备新的课程计划和媒资信息关联信息
        TeachplanMedia teachplanMedia = TeachplanMedia.builder()
                .mediaId(bindTeachPlanMediaDTO.getMediaId())
                .teachplanId(bindTeachPlanMediaDTO.getTeachplanId())
                .courseId(teachplan.getCourseId())
                .mediaFilename(bindTeachPlanMediaDTO.getFileName())
                .createDate(LocalDateTime.now())
                .build();
        // 4. 插入新的课程计划和媒资信息关联信息
        int insert = teachplanMediaMapper.insert(teachplanMedia);
        if (insert <= 0) CourseFailedBindMediaException.cast();
    }

    /**
     * 删除课程计划和媒资信息绑定
     *
     * @param teachPlanId 课程计划id
     * @param mediaId     媒资文件id
     */
    @Override
    public void deleteMedia(Long teachPlanId, String mediaId) {
        teachplanMediaMapper.deleteByTeachPlanIdAndMediaId(teachPlanId, mediaId);
    }

    /**
     * 根据课程计划id查询课程计划和媒资信息绑定信息
     *
     * @param teachplanId 课程计划id
     * @return 课程计划和媒资信息绑定信息
     */
    @Override
    public TeachPlanAndMediaDTO getTeachplanMedia(Long teachplanId) {
        TeachplanMedia teachplanMedia = teachplanMediaMapper.selectOne(new LambdaQueryWrapper<TeachplanMedia>().eq(TeachplanMedia::getTeachplanId, teachplanId));
        Teachplan teachplan = teachplanMapper.selectOne(new LambdaQueryWrapper<Teachplan>().eq(Teachplan::getId, teachplanId));
        TeachPlanAndMediaDTO teachPlanAndMediaDTO = BeanUtil.copyProperties(teachplan, TeachPlanAndMediaDTO.class);
        BeanUtil.copyProperties(teachplanMedia, teachPlanAndMediaDTO);
        return teachPlanAndMediaDTO;
    }
}
